/**
 * edurender
 * ersw_main.c
 * (c) 2009 J. Brad Byrd
 */

#include "../../include/edurender/er.h"

#include "../er/er_assert.h"
#include "../er/er_math.h"

#include "ersw_types.h"
#include "ersw_vertex.h"
#include "ersw_raster.h"
#include "ersw_context.h"
#include "ersw_texture.h"

#include <string.h>

#pragma warning(disable : 4100) // No "unreferenced formal parameter" warnings in this file.

ERResult ER_API erInitialize()
{
	return ER_RESULT_OK;
}

ERResult ER_API erShutdown()
{
	erswDestroyAllContexts();
	
	return ER_RESULT_OK;
}

ERResult ER_API erCreateContext(ERuint width, ERuint height, void *display, ERContext *context)
{
	ERSWContext *swContext;
	
	ER_ASSERT(display);
	ER_ASSERT(context);
	
	swContext = erswCreateContext((ERushort)width, (ERushort)height, display);
	ER_ASSERT(swContext);
	
	*context = swContext;
	
	return ER_RESULT_OK;
}

ERResult ER_API erDestroyContext(ERContext context)
{
	if (context == NULL)
		return ER_RESULT_INVALID_ARGUMENT;
		
	erswDestroyContext((ERSWContext *)context);

	return ER_RESULT_OK;
}

ERResult ER_API erCreateTexture2D(ERContext context, ERuint width, ERuint height,
	ERFormat format, ERTexture2D *texture)
{
	ERSWContext *c;
	ERSWTexture2D *t;
	
	if (texture != NULL)
		*texture = ER_INVALID_HANDLE;
	
	if (context == NULL)
		return ER_RESULT_INVALID_ARGUMENT;
	
	if (!ER_IS_POWER_OF_2(width))
		return ER_RESULT_INVALID_ARGUMENT;
	
	if (width > ERSW_MAX_TEXTURE_SIZE)
		return ER_RESULT_INVALID_ARGUMENT;
	
	if (!ER_IS_POWER_OF_2(height))
		return ER_RESULT_INVALID_ARGUMENT;
	
	if (height > ERSW_MAX_TEXTURE_SIZE)
		return ER_RESULT_INVALID_ARGUMENT;
	
	if (texture == NULL)
		return ER_RESULT_INVALID_ARGUMENT;
		
	c = (ERSWContext *)context;
	t = erswCreateTexture2D((ERushort)width, (ERushort)height, format);
	
	if (t == NULL)
		return ER_RESULT_OUT_OF_MEMORY;
	
	*texture = erswContextAddResource(c, ERSW_RESOURCE_TEXTURE2D, t);

	return ER_RESULT_OK;	
}

ERResult ER_API erUploadTexture2D(ERContext context, ERTexture2D texture, ERuint mip,
	const void *texels)
{
	ERSWContext *c;
	ERSWTexture2D *t;

	if (context == NULL)
		return ER_RESULT_INVALID_ARGUMENT;
	
	c = (ERSWContext *)context;
	t = erswContextFindResource(c, ERSW_RESOURCE_TEXTURE2D, texture);
	
	if (t == NULL)
		return ER_RESULT_INVALID_ARGUMENT;

	if (mip > t->mips)
		return ER_RESULT_INVALID_ARGUMENT;

	erswTexture2DUpload(t, (ERushort)mip, texels);
	
	return ER_RESULT_OK;	
}

ERResult ER_API erDestroyTexture2D(ERContext context, ERTexture2D texture)
{
	ERSWContext *c;
	ERSWTexture2D *t;

	if (context == NULL)
		return ER_RESULT_INVALID_ARGUMENT;
	
	c = (ERSWContext *)context;
	
	t = erswContextFindResource(c, ERSW_RESOURCE_TEXTURE2D, texture);
	
	if (t == NULL)
		return ER_RESULT_INVALID_ARGUMENT;
		
	erswDestroyTexture2D(t);
	erswContextRemoveResource(c, ERSW_RESOURCE_TEXTURE2D, texture);

	return ER_RESULT_OK;	
}

ERResult ER_API erClearColorBuffer(ERContext context, ERfloat red, ERfloat green, ERfloat blue, ERfloat alpha)
{
	ERSWContext *c;
	ERushort x, y;
	ERBgrColor color;

	c = (ERSWContext *)context;
	
	if (c == NULL)
		return ER_RESULT_INVALID_ARGUMENT;

	color.r = (ERubyte)(erSaturate(red) * 255.0f);
	color.g = (ERubyte)(erSaturate(green) * 255.0f);
	color.b = (ERubyte)(erSaturate(blue) * 255.0f);

	for (y = 0; y < c->framebuffer.height; ++y)
	{
		ERBgrColor *dst = &c->framebuffer.colors[y * c->framebuffer.stride];
		
		for (x = 0; x < c->framebuffer.width; ++x)
			*dst++ = color;
	}

	return ER_RESULT_OK;
}

ERResult ER_API erClearDepthBuffer(ERContext context, ERfloat depth)
{
	ERSWContext *c = (ERSWContext *)context;
	ERushort x, y;
	
	if (c == NULL)
		return ER_RESULT_INVALID_ARGUMENT;

	for (y = 0; y < c->framebuffer.height; ++y)
	{
		ERint *dst = &c->framebuffer.depths[y * c->framebuffer.stride];
		
		for (x = 0; x < c->framebuffer.width; ++x)
			*dst++ = (ERint)(depth * 0x7FFFFFFF - 1.0f);
	}

	return ER_RESULT_OK;
}

ERResult ER_API erSetViewport(ERContext context, ERint x, ERint y, ERuint width, ERuint height)
{
	ERSWContext *c = (ERSWContext *)context;

	if (c == NULL)
		return ER_RESULT_INVALID_ARGUMENT;

	c->viewport.x = x;
	c->viewport.y = y;
	c->viewport.width = width;
	c->viewport.height = height;

	return ER_RESULT_OK;
}

ERResult ER_API erSetMatrix(ERContext context, ERMatrix id, ERMatrixStorage storage, const ERfloat *matrix)
{
	ERSWContext *c = (ERSWContext *)context;
	ERMatrix4x4f *dst = NULL;
	
	if (c == NULL)
		return ER_RESULT_INVALID_ARGUMENT;

	if (matrix == NULL)
		return ER_RESULT_INVALID_ARGUMENT;
		
	switch (id)
	{
		case ER_MATRIX_MODELVIEW:
			dst = &c->modelview;
			break;
			
		case ER_MATRIX_PROJECTION:
			dst = &c->projection;
			break;
			
		case ER_MATRIX_TEXTURE_0:
		case ER_MATRIX_TEXTURE_1:
			return ER_RESULT_INVALID_ARGUMENT;
	}

	if (storage == ER_MATRIX_STORAGE_ROW_MAJOR)
		memcpy(dst, matrix, sizeof(ERfloat) * 16);
	else
		erTransposeMatrix(dst, (const ERMatrix4x4f *)matrix);

	return ER_RESULT_OK;
}

ERResult ER_API erSetDepthTest(ERContext context, ERbool enable)
{
	ERSWContext *c = (ERSWContext *)context;
	
	if (c == NULL)
		return ER_RESULT_INVALID_ARGUMENT;

	c->depthEnable = enable;

	return ER_RESULT_OK;
}

ERResult ER_API erSetDepthMask(ERContext context, ERbool enable)
{
	ERSWContext *c = (ERSWContext *)context;
	
	if (c == NULL)
		return ER_RESULT_INVALID_ARGUMENT;

	c->depthMask = enable;

	return ER_RESULT_OK;
}

ERResult ER_API erSetFrontFace(ERContext context, ERFrontFace frontFace)
{
	return ER_RESULT_OK;
}

ERResult ER_API erSetCullFace(ERContext context, ERCullFace cullFace)
{
	return ER_RESULT_OK;
}

ERResult ER_API erSetTexture(ERContext context, ERuint slot, ERTexture2D texture)
{
	ERSWContext *c = (ERSWContext *)context;
	ERSWTexture2D *t;
	
	if (c == NULL)
		return ER_RESULT_INVALID_ARGUMENT;
	
	if (slot > 2)
		return ER_RESULT_INVALID_ARGUMENT;

	t = erswContextFindResource(c, ERSW_RESOURCE_TEXTURE2D, texture);
	
	if (t == NULL)
		return ER_RESULT_INVALID_ARGUMENT;
		
	c->texture = t;
	
	return ER_RESULT_OK;
}

ERResult ER_API erSetPositionArray(ERContext context, ERuint stride, const ERfloat *positions)
{
	ERSWContext *c = (ERSWContext *)context;
	
	if (c == NULL)
		return ER_RESULT_INVALID_ARGUMENT;
		
	c->posArray = positions;
	c->posStride = stride;

	return ER_RESULT_OK;
}

ERResult ER_API erSetTexCoordArray(ERContext context, ERuint stride, const ERfloat *texCoords, ERuint slot)
{
	ERSWContext *c = (ERSWContext *)context;
	
	if (c == NULL)
		return ER_RESULT_INVALID_ARGUMENT;
	
	if (slot > 2)
		return ER_RESULT_INVALID_ARGUMENT;
		
	c->texCoordArray[slot] = texCoords;
	c->texCoordStride[slot] = stride;

	return ER_RESULT_OK;
}

ERResult ER_API erSetColorArray(ERContext context, ERuint stride, const ERuint *colors)
{
	return ER_RESULT_OK;
}

static ERbool IsCulled(const ERSWContext *context, ERuint a, ERuint b, ERuint c, ERbool stripFlip, ERFrontFace *face)
{
	const ERSWOutputVertex *out = context->outputVerts;
	//ERbool culled;
	
	ERVector3f ab;
	ERVector3f ac;
	
	ER_ASSERT(face);
	
	ab.x = out[b].spos.x - out[a].spos.x;
	ab.y = out[b].spos.y - out[a].spos.y;
	ac.x = out[c].spos.x - out[a].spos.x;
	ac.y = out[c].spos.y - out[a].spos.y;
	
	*face = (ab.x * ac.y) < (ab.y * ac.x) ? ER_FRONT_FACE_CW : ER_FRONT_FACE_CCW;
	
	return ER_FALSE;
}

ERResult ER_API erDraw(ERContext context, ERPrimitive primitive, ERuint vertexCount, ERuint offset)
{
	ERSWContext *c = (ERSWContext *)context;
	ERuint i;
	const ERfloat *posSrc;
	const ERfloat *texCoordSrc;
	ERSWInputVertex *dst;
	ERuint posStride;
	ERuint texCoordStride;

	ERSWVertexState vs;
	ERSWRasterState rs;
	
	if (c == NULL)
		return ER_RESULT_INVALID_ARGUMENT;
		
	// JBB_TODO: Avoid this copy!
	posSrc = c->posArray;
	posStride = c->posStride / sizeof(ERfloat);
	dst = c->inputVerts;
	
	texCoordSrc = c->texCoordArray[0];
	texCoordStride = c->texCoordStride[0] / sizeof(ERfloat);
	
	for (i = 0; i < vertexCount; ++i)
	{
		memcpy(&dst->position, posSrc, sizeof(ERfloat) * 3);
		
		if (texCoordSrc != NULL)
			memcpy(&dst->texCoord[0], texCoordSrc, sizeof(ERfloat) * 2);
		
		posSrc += posStride;
		texCoordSrc += texCoordStride;
		
		dst++;
	}

	vs.modelview = c->modelview;
	vs.projection = c->projection;
	vs.viewport = c->viewport;
	erswTransformVertices(&vs, c->inputVerts, c->outputVerts, vertexCount);

	rs.depthEnable = c->depthEnable;
	rs.depthMask = c->depthMask;
	rs.framebuffer = &c->framebuffer;
	rs.viewport = &c->viewport;
	rs.texture = c->texture;

	if (vertexCount < 2)
		return ER_RESULT_OK;

	switch (primitive)
	{
		ERuint j;
		ERFrontFace face;
		
		case ER_PRIMITIVE_TRIANGLES:
		{
			for (j = 0; j < vertexCount; j += 3)
				if (!IsCulled(c, j, j + 1, j + 2, ER_FALSE, &face))
					erswRasterizeTriangle(&rs, &c->outputVerts[j], &c->outputVerts[j + 1], &c->outputVerts[j + 2], face);
				
			break;
		}
		
		case ER_PRIMITIVE_TRIANGLE_STRIP:
		{
			if (!IsCulled(c, 0, 1, 2, ER_FALSE, &face))
				erswRasterizeTriangle(&rs, &c->outputVerts[0], &c->outputVerts[1], &c->outputVerts[2], face);
			
			for (j = 1; j < vertexCount - 3; j++)
			{
				if (!IsCulled(c, j, j + 1, j + 2, ER_TRUE, &face))
					erswRasterizeTriangle(&rs, &c->outputVerts[j], &c->outputVerts[j + 1], &c->outputVerts[j + 2], face);
				
				if (!IsCulled(c, j + 1, j + 2, j + 3, ER_FALSE, &face))
					erswRasterizeTriangle(&rs, &c->outputVerts[j + 1], &c->outputVerts[j + 2], &c->outputVerts[j + 3], face);				
			}
		
			if ((vertexCount & 1) == 0 && !IsCulled(c, vertexCount - 3, vertexCount - 2, vertexCount - 1, ER_TRUE, &face))
				erswRasterizeTriangle(&rs, &c->outputVerts[vertexCount - 3], &c->outputVerts[vertexCount - 2],
					&c->outputVerts[vertexCount - 1], face);

			break;
		}
		
		case ER_PRIMITIVE_QUADS:
		{
			for (j = 0; j < vertexCount; j += 4)
			{
				if (!IsCulled(c, j + 1, j + 2, j + 3, ER_FALSE, &face))
				{
					erswRasterizeTriangle(&rs, &c->outputVerts[j], &c->outputVerts[j + 1], &c->outputVerts[j + 2], face);
					erswRasterizeTriangle(&rs, &c->outputVerts[j], &c->outputVerts[j + 2], &c->outputVerts[j + 3], face);
				}
			}
			
			break;
		}
	}
	
	return ER_RESULT_OK;
}

ERResult ER_API erDrawIndexed(ERContext context, ERPrimitive primitive,
	ERushort indexCount, ERuint offset, const ERushort *indices)
{
	return ER_RESULT_OK;
}

ERResult ER_API erSwapBuffers(ERContext context)
{
	ERSWContext *c;
	
	c = (ERSWContext *)context;
	
	if (c == NULL)
		return ER_RESULT_INVALID_ARGUMENT;
	
	return erswSwapBuffers(c);
}
